package com.dynamic.mybatis.core.generator;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.dynamic.mybatis.core.generator.method.*;
import com.dynamic.mybatis.core.metadata.DynamicMappedStatement;
import com.dynamic.mybatis.core.metadata.MappedStatementTable;
import com.dynamic.mybatis.core.utils.XmlUtil;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;

import java.util.*;

/**
 * @Description
 * @Author xs
 * @Date 2023/4/14 10:47
 */
public abstract class InjectorMethod implements Constants {

    protected String SPACE = "\t";
    protected MapperMethod mapperMethod;
    //protected Log logger = LogFactory.getLog(this.getClass());


    //key sqlMethod   value sql
    protected static Map<String, String> sqlMethodExtended = new HashMap<>();
    //key com.dynamic.mybatis.core.generator.MapperMethod.name()
    protected static Map<String, InjectorMethod> injectorMapperMethods = new LinkedHashMap<>();

    static {
        addInjectorMethod(new Save());
        addInjectorMethod(new SelectPage());
        addInjectorMethod(new SelectById());
        addInjectorMethod(new SelectList());
        addInjectorMethod(new DeleteById());
        addInjectorMethod(new Insert());
        addInjectorMethod(new Update());
        addInjectorMethod(new UpdateById());
        addInjectorMethod(new InsertBatch());
        addInjectorMethod(new UpdateBatch());
        addInjectorMethod(new SaveBatch());
        //扩展方式
        //InjectorMethod.put(MapperMethod.upsert.sqlMethod,"<script>\nUPSERT INTO %s %s VALUES %s\n</script>");
        //ddInjectorMethod(new Upsert());
    }

    private static void addInjectorMethod(InjectorMethod injectorMethod) {
        injectorMapperMethods.put(injectorMethod.mapperMethod.name(), injectorMethod);

        String sqlMethod = injectorMethod.mapperMethod.getSqlMethod();
        if (sqlMethod != null) {
            String sql = null;
            for (SqlMethod sm : SqlMethod.values()) {
                if (sqlMethod.equals(sm.getMethod())) {
                    sql = sm.getSql();
                    break;
                }
            }
            put(injectorMethod.mapperMethod.getSqlMethod(), sql);
        }
    }

    public static void put(String sqlMethod, String sql) {
        sqlMethodExtended.put(sqlMethod, sql);
    }


    public static <T extends InjectorMethod> T getByMapperMethod(String mapperMethod) {
        return (T) injectorMapperMethods.get(mapperMethod);
    }

    public String getSqlTemplate() {
        String sql = sqlMethodExtended.get(mapperMethod.getSqlMethod());
        if (StringUtils.isBlank(sql)) {
            for (SqlMethod sm : SqlMethod.values()) {
                if (mapperMethod.getSqlMethod().equalsIgnoreCase(sm.getMethod())) {
                    sqlMethodExtended.put(sm.getMethod(), sm.getSql());
                    return sm.getSql();
                }
            }
        }
        return sql;
    }


    public InjectorMethod(MapperMethod mapperMethod) {
        this.mapperMethod = mapperMethod;
    }

    /**
     * 注入自定义方法
     */
    public DynamicMappedStatement inject(String namespace, MapperBuilderAssistant assistant, MappedStatementTable table) {
        assistant.setCurrentNamespace(namespace);
        /* 注入自定义方法 */
        return injectMappedStatement(namespace, table, assistant);
    }

    public String replaceScriptTag(String sql) {
        return sql.replaceAll("^<script>\\s*|\\s*</script>$", "");
    }


    public String builderMapperXml(String sql, MappedStatementTable table, MappedStatement mappedStatement) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(LEFT_CHEV);
        String tagName = mappedStatement.getSqlCommandType().name().toLowerCase();
        stringBuilder.append(tagName);
        String id = mapperMethod + table.getTableName().substring(0, 1).toUpperCase() + StringUtils.underlineToCamel(table.getTableName().substring(1));
        stringBuilder.append(" id='" + id + "' ");
        stringBuilder.append(" parameterType='map' ");
        if (StringUtils.isNotBlank(mapperMethod.getResultType())) {
            stringBuilder.append(" resultType='" + mapperMethod.getResultType() + "' ");
        }
        if (mapperMethod.getReturnType() != null) {
            stringBuilder.append(" returnType = '" + mapperMethod.getReturnType().name().toLowerCase() + "' ");
        }
        if (mapperMethod.isBatch()) {
            stringBuilder.append(" batchExecute = 'true' ");
        }

        if (StringUtils.isNotBlank(mapperMethod.getRemarks())) {
            stringBuilder.append(" name='" + String.format(mapperMethod.getRemarks(), Objects.toString(table.getName(), "")) + "' ");
        }
        stringBuilder.append(RIGHT_CHEV);
        stringBuilder.append(NEWLINE);
        stringBuilder.append(SPACE);
        stringBuilder.append(replaceScriptTag(sql));
        stringBuilder.append(NEWLINE);
        stringBuilder.append(LEFT_CHEV);
        stringBuilder.append(SLASH);
        stringBuilder.append(tagName);
        stringBuilder.append(RIGHT_CHEV);
        stringBuilder.append(NEWLINE);
        stringBuilder.append(NEWLINE);
        return XmlUtil.javaApacheFormatPretty(stringBuilder.toString());
    }

    public abstract String builderScriptSql(String namespace, MappedStatementTable table, MapperBuilderAssistant assistant);

    public DynamicMappedStatement builderDynamicMappedStatement(String namespace, MappedStatementTable table, MappedStatement ms, String sql) {
        String shortId = mapperMethod.name() + table.getTableName().substring(0, 1).toUpperCase() + StringUtils.underlineToCamel(table.getTableName().substring(1));
        DynamicMappedStatement.Builder dmsBuilder = new DynamicMappedStatement().toBuilder()
                .setSqlStatement(builderMapperXml(sql, table, ms))
                .setId(shortId)
                .setNamespace(namespace)
                .setTables(new HashSet() {{
                    add(table.getTableName());
                }})
                .setBatchExecute(mapperMethod.isBatch())
                .setCommandType(ms.getSqlCommandType())
                .setName(String.format(Objects.toString(mapperMethod.getRemarks(), ""), Objects.toString(table.getName(), "")))
                .setReturnType(mapperMethod.getReturnType());
        dmsBuilder.setMethod(mapperMethod.getMethod());
        if (ms.getResultMaps() != null && ms.getResultMaps().size() > 0) {
            // dmsBuilder.setResultMap(ms.getResultMaps().get(0).getId());
            dmsBuilder.setResultType(ms.getResultMaps().get(0).getType().getName());
        }
        if (ms.getParameterMap() != null) {
            dmsBuilder.setParameterMap(ms.getParameterMap().getId());
        }
        return dmsBuilder.builder();
    }


    public MappedStatement addMappedStatement(
            MapperBuilderAssistant assistant,
            MappedStatementTable table,
            String namespace,
            SqlSource sqlSource,
            SqlCommandType sqlCommandType,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            KeyGenerator keyGenerator,
            String keyProperty,
            String keyColumn) {

        String id = namespace + DOT + mapperMethod.name() + table.getTableName().substring(0, 1).toUpperCase() + StringUtils.underlineToCamel(table.getTableName().substring(1));

        LanguageDriver languageDriver = assistant.getConfiguration().getDefaultScriptingLanguageInstance();
        Configuration configuration = assistant.getConfiguration();

        if (hasMappedStatement(configuration, id)) {
            //logger.warn(LEFT_SQ_BRACKET + id +RIGHT_SQ_BRACKET+ " Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
        }
        /* 缓存逻辑处理 */
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        return assistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
                null, null, null, parameterType, resultMap, resultType,
                null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
                configuration.getDatabaseId(), languageDriver, null);
    }

    /**
     * 是否已经存在MappedStatement
     *
     * @param mappedStatement MappedStatement
     * @return true or false
     */
    protected boolean hasMappedStatement(Configuration configuration, String mappedStatement) {
        return configuration.hasStatement(mappedStatement, false);
    }

    public abstract DynamicMappedStatement injectMappedStatement(String namespace, MappedStatementTable table, MapperBuilderAssistant assistant);
}